1

顺风车运营研发团队 施洪宝

getbit, getrange, blpop, brpop, rpop

一. getbit
1.命令说明
使用方式: getbit key offset

功能: 对key对应value的值, 取对应偏移量上的值。

返回值: key不存在, offset比字符串长度大时, 返回0, 否则返回对应位上的值。

时间复杂度: O(1)

2.源码实现
源码实现的步骤可以分为两步: 1. 获取偏移量所在字节数。 2. 获取偏移量所在字节的bit。

void getbitCommand(client *c) {
    robj *o;
    char llbuf[32];
    size_t bitoffset;
    size_t byte, bit;
    size_t bitval = 0;
    //读取参数
    if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset,0,0) != C_OK)
        return;
 
    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
        checkType(c,o,OBJ_STRING)) return;
    //获取offset对应的字节位置以及对应的bit
    byte = bitoffset >> 3;
    bit = 7 - (bitoffset & 0x7);
 
    //读取结果并返回
    if (sdsEncodedObject(o)) {
        if (byte < sdslen(o->ptr))
            bitval = ((uint8_t*)o->ptr)[byte] & (1 << bit);
    } else {
        if (byte < (size_t)ll2string(llbuf,sizeof(llbuf),(long)o->ptr))
            bitval = llbuf[byte] & (1 << bit);
    }
 
    addReply(c, bitval ? shared.cone : shared.czero);
}

3. 参考
http://doc.redisfans.com/stri...

二. getrange
1.命名说明
使用方式:getrange key start end

功能: 返回key中字符串值得子字符串,字符串截取位置由start, end决定

返回值: 截取的子字符串,超过范围自动截取

时间复杂度: O(n)

2.源码实现

void getrangeCommand(client *c) {
    robj *o;
    long long start, end;
    char *str, llbuf[32];
    size_t strlen;
 
    //获取参数
    if (getLongLongFromObjectOrReply(c,c->argv[2],&start,NULL) != C_OK)
        return;
    if (getLongLongFromObjectOrReply(c,c->argv[3],&end,NULL) != C_OK)
        return;
    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptybulk)) == NULL ||
        checkType(c,o,OBJ_STRING)) return;
 
    //获取值对应的字符串
    if (o->encoding == OBJ_ENCODING_INT) {
        str = llbuf;
        strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
    } else {
        str = o->ptr;
        strlen = sdslen(str);
    }
 
    //start, end处理
    /* Convert negative indexes */
    if (start < 0 && end < 0 && start > end) {
        addReply(c,shared.emptybulk);
        return;
    }
    if (start < 0) start = strlen+start;
    if (end < 0) end = strlen+end;
    if (start < 0) start = 0;
    if (end < 0) end = 0;
    if ((unsigned long long)end >= strlen) end = strlen-1;
 
    /* Precondition: end >= 0 && end < strlen, so the only condition where
     * nothing can be returned is: start > end. */
    if (start > end || strlen == 0) {
        addReply(c,shared.emptybulk);
    } else {
        addReplyBulkCBuffer(c,(char*)str+start,end-start+1);
    }
}

3.参考
http://doc.redisfans.com/stri...
三. blpop
1.命令说明
使用方法: blpop key [key...] timeout

功能: 从给定的key中,找到一个非空列表,返回头元素。查找顺序按照给定的key顺序。如果所有key对应的列表都是空的,则操作被阻塞,阻塞时间可由timeout设置

时间复杂度: O(n), n为key的个数(没有被阻塞的情况下)

返回值: 返回key以及列表的元素

2.源码实现

void blockingPopGenericCommand(client *c, int where) {
    robj *o;
    mstime_t timeout;
    int j;
 
    //获取超时时间
    if (getTimeoutFromObjectOrReply(c,c->argv[c->argc-1],&timeout,UNIT_SECONDS)
        != C_OK) return;
 
    //依次遍历所有给定的key
    for (j = 1; j < c->argc-1; j++) {
        //获取key对应的value
        o = lookupKeyWrite(c->db,c->argv[j]);
        if (o != NULL) {
            if (o->type != OBJ_LIST) {
                addReply(c,shared.wrongtypeerr);
                return;
            } else {
                if (listTypeLength(o) != 0) {
                    /* Non empty list, this is like a non normal [LR]POP. */
                    char *event = (where == LIST_HEAD) ? "lpop" : "rpop";
                    robj *value = listTypePop(o,where); //从列表中弹出一个元素,位置由where决定, 头部或者尾部。
                    serverAssert(value != NULL);
                 
                    //返回结果
                    addReplyMultiBulkLen(c,2);
                    addReplyBulk(c,c->argv[j]);
                    addReplyBulk(c,value);
                    decrRefCount(value);
                    //发送键空间事件通知(pub, sub)
                    notifyKeyspaceEvent(NOTIFY_LIST,event,
                                        c->argv[j],c->db->id);
                    if (listTypeLength(o) == 0) {
                        dbDelete(c->db,c->argv[j]);
                        notifyKeyspaceEvent(NOTIFY_GENERIC,"del",
                                            c->argv[j],c->db->id);
                    }
                    //通知正在watch这个key的client, 具体实现为将client的flags开启CLIENT_DIRTY_CAS
                    signalModifiedKey(c->db,c->argv[j]);
                    server.dirty++;
 
                    /* Replicate it as an [LR]POP instead of B[LR]POP. */
                    //因为已经在一个key上找到元素,故而可以将blpop, brpop改写为lpop, rpop
                    rewriteClientCommandVector(c,2,
                        (where == LIST_HEAD) ? shared.lpop : shared.rpop,
                        c->argv[j]);
                    return;
                }
            }
        }
    }
 
    /* If we are inside a MULTI/EXEC and the list is empty the only thing
     * we can do is treating it as a timeout (even with timeout 0). */
    //Redis正在执行事务
    if (c->flags & CLIENT_MULTI) {
        addReply(c,shared.nullmultibulk);
        return;
    }
 
    /* If the list is empty or the key does not exists we must block */
    //列表为空或者key不存在
    blockForKeys(c, c->argv + 1, c->argc - 2, timeout, NULL);
}

3. 参考
http://doc.redisfans.com/list...

四. brpop
1.命令说明
使用方法: rlpop key [key...] timeout

功能: 从给定的key中,找到一个非空列表,返回尾部元素。查找顺序按照给定的key顺序。如果所有key对应的列表都是空的,则操作被阻塞,阻塞时间可由timeout设置

时间复杂度: O(n), n为key的个数(没有被阻塞的情况下)

返回值: 返回key以及列表的元素

2.源码实现
与blpop相同,二者都是通过调用blockingPopGenericCommand来实现的,唯一的区别再与,blpop弹出首部(blockingPopGenericCommand的where参数为LIST_HEAD),brpop弹出尾部元素(blockingPopGenericCommand的where参数为LIST_TAIL)

3. 参考
http://doc.redisfans.com/list...

五. rpop
1.命令说明
使用方法: rpop key

功能: 移除并返回列表的尾部元素。

返回值: 返回列表的尾部元素,不存在时返回nil

时间复杂度: O(1)

2. 源码实现

void popGenericCommand(client *c, int where) {
    robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk); //查找key
    if (o == NULL || checkType(c,o,OBJ_LIST)) return;
 
    robj *value = listTypePop(o,where);
    if (value == NULL) {
        addReply(c,shared.nullbulk);  //value中没有元素
    } else {
        char *event = (where == LIST_HEAD) ? "lpop" : "rpop";
 
        addReplyBulk(c,value);  //返回结果
        decrRefCount(value);
        notifyKeyspaceEvent(NOTIFY_LIST,event,c->argv[1],c->db->id); //键空间通知
        if (listTypeLength(o) == 0) {
            notifyKeyspaceEvent(NOTIFY_GENERIC,"del",
                                c->argv[1],c->db->id);
            dbDelete(c->db,c->argv[1]);
        }
        signalModifiedKey(c->db,c->argv[1]);  //通知正在监听这个key的client, 具体实现为将client的flags开启CLIENT_DIRTY_CAS
        server.dirty++;   //脏标志加1
    }
}

3.参考
http://doc.redisfans.com/list...


AI及LNMPRG研究
7.2k 声望12.8k 粉丝

一群热爱代码的人 研究Nginx PHP Redis Memcache Beanstalk 等源码 以及一群热爱前端的人